#!/usr/bin/python3

# this whole thing was a *huge* pain to figure out. i decided to document the code *very* verbosely, so that if someone, ever stumbles across a similar problem, it'll be a bit less of a headache for them

# Usage: add 'exec_always /path/to/lang.py' to your sway config and adjust timeout to your preference

TIMEOUT = 700
CAPS_LOCK_FILE = '/sys/class/leds/input3::capslock/brightness'

import os, struct, socket, json, gi
gi.require_version("Notify", "0.7")
from gi.repository import Notify

# 'magic' is the 'magic string' that must be sent at the beginning of every sway-ipc message. As per manpage:
# The format for messages and replies is:
# <magic-string> <payload-length> <payload-type> <payload>
# Where <magic-string> is i3-ipc, for compatibility with i3
# <payload-length> is a 32-bit integer in native byte order
# <payload-type> is a 32-bit integer in native byte order

magic, cmd = b'i3-ipc', b'["input"]'

def rec():
    resp = s.recv(len(prefix))
    # decoding <payload-length> and <payload-type> (ii means integer integer, as in we're decoding two integers)
    size, code = struct.unpack('ii', resp[len(magic):])
    return s.recv(size)

# opens up a socket connection to SWAYSOCK, grabs its path from environment
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(os.environ['SWAYSOCK'])
# type 2 is the SUBSCRIBE message, see sway-ipc(7) for all message types
prefix = magic + struct.pack('ii', len(cmd), 2)
s.sendall(prefix + cmd)

out = rec()
if not json.loads(out)['success']:
    print('\033[0;31mError connecting to sway-ipc! Error message: \n', out.decode(), '\033[0;0m', sep = '')

Notify.init('swaylang')
lang = ''
msg = Notify.Notification.new('', '')
while True:
    # for some reason, sway-ipc sends multiple layout change event messages each time a change is detected, which is the whole reason why this couldn't be a shell one-liner in the first place. so, as a workaround, we store the old layout in a variable, and only notify the user if it is actually changed
    newLang = json.loads(rec())['input']['xkb_active_layout_name']
    if lang != newLang:
        lang = newLang
        f = open(CAPS_LOCK_FILE, 'r')
        if msg:
            msg.close()
        msg = Notify.Notification.new(lang, 'Caps Lock' if '1' in f.read() else '')
        msg.set_timeout(TIMEOUT)
        msg.show()
